package org.slusk.thynwor.feedback;

import java.util.LinkedList;

import org.slusk.thynwor.corestructures.Drone;
import org.slusk.thynwor.corestructures.Drone.MoveOrder;
import org.slusk.thynwor.corestructures.Environment;
import org.slusk.thynwor.corestructures.Food;
import org.slusk.thynwor.corestructures.GridMap;
import org.slusk.thynwor.corestructures.Region;
import org.slusk.thynwor.corestructures.Region.RegionType;
import org.slusk.thynwor.util.Coordinates;
import org.slusk.thynwor.util.Direction;

import com.anji.hyperneat.nd.NDFloatArray;
import com.anji.hyperneat.onlinereinforcement.trainingbank.Feedback;
import com.anji.hyperneat.onlinereinforcement.trainingbank.Feedback.FeedbackType;
import com.anji.hyperneat.onlinereinforcement.trainingbank.TrainingSample;

public class SupervisedLearningProvider extends AbstractFeedbackProvider {

	public SupervisedLearningProvider(Environment environment) {
		super(environment);
	}

	@Override
	public Feedback getFeedback(Drone bot, MoveOrder mo, NDFloatArray inputs, Coordinates outputCoords, NDFloatArray outputs) {
		Feedback feedback = getExpectedOutputs(bot, mo, inputs, outputs);
		return feedback;
	}

	private Feedback getExpectedOutputs(Drone bot, MoveOrder mo, NDFloatArray inputs, NDFloatArray outputs) {
		NDFloatArray expected = null;
		Coordinates coords = bot.getCoordinates();
		FeedbackType type = FeedbackType.REINFORCEMENT;
		
		if (bot.isTowingResource()) {
			// If it's towing a resource, it should head for the base.
			Region base = map().getClosestRegionOfType(coords , RegionType.BASE);
			Coordinates baseCenter = base.getCenter();
			Direction desiredDirection = GridMap.coordinatesToDirection(coords, baseCenter);
			expected = findAppropriateDirection(bot, mo.proposedDirection, desiredDirection);
		} else {
			// Not towing? It should find something to tow
			// Look for signals first, then for food.
			int signalInputIndex = 2;
			Coordinates bestSig = getMaxFromInputDim(inputs, signalInputIndex);
			if (inputs.get(bestSig.x, bestSig.y, signalInputIndex) > 0.0f) {
				bestSig = bot.getActualCoordsFromOutputCoords(bestSig);
				Direction desiredDirection = GridMap.coordinatesToDirection(coords, bestSig);
				expected = findAppropriateDirection(bot, mo.proposedDirection, desiredDirection);
			
			} 
			// No signals, get nearest food
			else {
				signalInputIndex = 1;
				Coordinates bestFood = getMaxFromInputDim(inputs, signalInputIndex/*, sindex*/);
				
				// Bots have a limited sight range.  If they can't see food, they should search.  Not sure how to make this happen.
				// But for now, send a null direction, and we'll see what happens.
				Direction desiredDirection;
				if (inputs.get(bestFood.x, bestFood.y, signalInputIndex/*, sindex*/) == 0.0f) {
					desiredDirection = null;
				} else {
					bestFood = bot.getActualCoordsFromOutputCoords(bestFood);
					desiredDirection = GridMap.coordinatesToDirection(coords, bestFood);
				}
				expected = findAppropriateDirection(bot, mo.proposedDirection, desiredDirection);
			}
		}
		if (null == expected) {
			expected = outputs.clone();
			type = FeedbackType.NEUTRAL;
		}
		return new Feedback(expected, type);
	}

	protected NDFloatArray findAppropriateDirection(Drone bot, Direction actual, Direction desiredDirection) {
		NDFloatArray expected = null;
		Coordinates coords = bot.getCoordinates();
		
		if (desiredDirection == null) {
			return null;	// For now do nothing, just let net decide what do
		}
		
		Coordinates desiredCoords = GridMap.directionToCoordinates(desiredDirection, coords);
		// Easy case, the desired direction is free, or food if not towing
		if (map().isFree(desiredCoords) || (!bot.isTowingResource() && map().get(desiredCoords).getContents() instanceof Food)) {
			expected = directionToOutput(desiredDirection);
		} 
		// That direction not free? What else is good.
		else {
			
			// This list shouldn't contain the desired direction because it is not free
			LinkedList<Direction> similarFreeDirections = map().getSimilarAvailableDirections(coords, desiredDirection);
			
			if (similarFreeDirections.size() > 0) {
				// If the net chose one of these, just reinforce the net's decision
				if (similarFreeDirections.contains(actual)) {
					expected = directionToOutput(actual);
				} 
				// Otherwise, reinforce both directions.  This probably breaks a tennet of backprop, but should trend the net toward better behavior. 
				else {
					expected = directionsToOutput(similarFreeDirections);
				}
			} else {
				// that sucks.  none of the desirable directions were free
				// So at this point the any free directions that exist must be not in the general direction of the desired.
				LinkedList<Direction> remainingDirs = map().getAvailableDirections(coords);
				if (remainingDirs.size() > 0) {
					// If the net chose one of these, just reinforce the net's decision
					if (remainingDirs.contains(actual)) {
						expected = directionToOutput(actual);
					} 
					// Stupid net doesn't know what's good for it.
					// reinforce everything else.  Probably not a great idea, but I don't have all night.
					expected = directionsToOutput(remainingDirs);
				} 
				// Bot is completely blocked in.  How'd that happen?
				// Just have it point toward the goal anyway.
				expected = directionToOutput(desiredDirection);
			}
		}
		
		return expected;
	}

	private NDFloatArray directionToOutput(Direction direction) {
		NDFloatArray expected = new NDFloatArray(3,3,1);
		expected.clear();
		if (null == direction) {
			expected.set(1.0f, 1, 1);
		} else {
			expected.set(1.0f
					, 1 + GridMap.neighborRelativeCoords[direction.ordinal()].x
					, 1 + GridMap.neighborRelativeCoords[direction.ordinal()].y
					, 0
			);
		}
		
		return expected;
	}
	

	private NDFloatArray directionsToOutput(LinkedList<Direction> directions) {
		NDFloatArray expected = new NDFloatArray(3,3,1);
		expected.clear();
		if (null == directions || directions.size() == 0) {
			expected.set(1.0f, 1, 1);
		} else {
			for (Direction d : directions) {
				expected.set(1.0f
						, 1 + GridMap.neighborRelativeCoords[d.ordinal()].x
						, 1 + GridMap.neighborRelativeCoords[d.ordinal()].y
						, 0
				);
			}
		}
		return expected;
	}

	@Override
	public void convertTrainingSampleToReinforcement(ThynworTrainingSample sample, float weight) {
//		NDFloatArray value = sample.outputs.clone();
//		value.set(weight, sample.outputCoords.x, sample.outputCoords.y, 0);
//		sample.feedback = new Feedback(value, FeedbackType.REINFORCEMENT);
		sample.feedback.value.set(1.0f, sample.outputCoords.x, sample.outputCoords.y, 0);
		sample.weight = weight;
		sample.feedback.type = FeedbackType.REINFORCEMENT;
	}

	@Override
	public void convertTrainingSampleToPunishment(ThynworTrainingSample sample) {
		// TODO Auto-generated method stub
		
	}	

}
